# coding:utf-8
import socket
import threading
from Helper import *
import time
import traceback


class client:
    def __init__(self, buffer_size,target):
        self.lock = threading.Lock()
        self.max_speed = 1024 * 1024 * 1
        self.UDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.UDP.sendto(b'hello', target)
        self.Send_Transfers = {}
        self.Recv_Transfers = {}
        self.loss_notice = {}
        self.transfer_timeout = 1
        self.piece_timeout = 0.5
        self.buffer_size = buffer_size
        self.clean_time_interval = 30
        threading.Thread(target=self.Recv).start()
        threading.Thread(target=self.Check_Handle).start()
        threading.Thread(target=self.clean).start()

    def Recv(self):
        while True:
            recv_data, dest_host = self.UDP.recvfrom(self.buffer_size)
            print('recv:',dest_host, recv_data[:100])
            self.Recv_Handle(recv_data, dest_host)

    def Join_Data(self, crc32, piece_id, data, count, dest_host):
        if crc32 in list(self.Recv_Transfers.keys()):
            if piece_id not in self.Recv_Transfers[crc32]['pieces'].keys():
                self.Recv_Transfers[crc32]['pieces'][piece_id] = {'data': data, 'atime': time.time()}
                self.Recv_Transfers[crc32]['recv_count'] += 1
                self.Recv_Transfers[crc32]['atime'] = time.time()
        else:
            self.Recv_Transfers[crc32] = {
                'status': 0,
                'ctime': time.time(),
                'atime': time.time(),
                'dest_host': dest_host,
                'count': count,
                'recv_count': 1,
                'pieces': {piece_id: {'data': data, 'atime': time.time()}}
            }
    def RecvData(self):
        try:
            for crc32 in list(self.Recv_Transfers.keys()):
                if self.Recv_Transfers[crc32]['status'] == 1:
                    pieces = list(self.Recv_Transfers[crc32]['pieces'].keys())
                    pieces.sort()
                    data_list = []
                    for piece_id in list(self.Recv_Transfers[crc32]['pieces'].keys()):
                        data_list.append(self.Recv_Transfers[crc32]['pieces'][piece_id]['data'])
                    data = b''.join(data_list)
                    self.Recv_Transfers[crc32]['status'] = 2
                    return data
        except:
            return False

    def Check_Handle(self):
        # 通知丢包 可做线程
        while True:
            try:
                for crc32 in list(self.Recv_Transfers.keys()):
                    if self.Recv_Transfers[crc32]['count'] == self.Recv_Transfers[crc32]['recv_count'] and \
                            self.Recv_Transfers[crc32]['status'] == 0:
                        '''3次完成通知'''
                        self.UDP.sendto(('E' + crc32).encode(), self.Recv_Transfers[crc32]['dest_host'])
                        time.sleep(0.0001)
                        self.UDP.sendto(('E' + crc32).encode(), self.Recv_Transfers[crc32]['dest_host'])
                        time.sleep(0.0001)
                        self.UDP.sendto(('E' + crc32).encode(), self.Recv_Transfers[crc32]['dest_host'])
                        time.sleep(0.0001)
                        # print('complete')
                        # print('Usedtime:', time.time() - self.Recv_Transfers[crc32]['ctime'])
                        # print(list(self.Recv_Transfers.keys()))
                        self.Recv_Transfers[crc32]['status'] = 1
                        continue
                    # print('check loss:', crc32, self.Recv_Transfers[crc32]['recv_count'],
                    #       self.Recv_Transfers[crc32]['count'])

                    if self.Recv_Transfers[crc32]['recv_count'] < self.Recv_Transfers[crc32]['count'] \
                            and time.time() - self.Recv_Transfers[crc32]['atime'] > self.piece_timeout \
                            and self.Recv_Transfers[crc32]['status'] == 0:

                        if crc32 not in self.loss_notice:
                            self.loss_notice[crc32] = {}
                        print('loss count:',
                              self.Recv_Transfers[crc32]['count'] - self.Recv_Transfers[crc32]['recv_count'])
                        recv_piece_ids = list(self.Recv_Transfers[crc32]['pieces'].keys())
                        losses = set(list(range(0, self.Recv_Transfers[crc32]['count']))) - set(recv_piece_ids)
                        loss_group = []
                        send_data = b''
                        header = ('D' + crc32).encode()
                        for index, piece_id in enumerate(losses):
                            if (
                                    piece_id in self.loss_notice[crc32].keys()
                                    and time.time() - self.loss_notice[crc32][piece_id] > self.piece_timeout
                            ) or piece_id not in self.loss_notice[crc32].keys():
                                if piece_id not in self.loss_notice[crc32].keys():
                                    self.loss_notice[crc32][piece_id] = time.time()
                                if len(send_data) + len((',' + str(piece_id)).encode()) < self.buffer_size - len(
                                        header):
                                    loss_group.append(str(piece_id))
                                    send_data = header + ','.join(loss_group).encode()
                                else:
                                    print('send D:', send_data[:50])
                                    self.UDP.sendto(send_data, self.Recv_Transfers[crc32]['dest_host'])
                                    send_data = b''
                                    loss_group = [str(piece_id)]
                                if index == len(losses) - 1:
                                    send_data = header + ','.join(loss_group).encode()
                                    # print('send Last:', send_data[:50])
                                    self.UDP.sendto(send_data, self.Recv_Transfers[crc32]['dest_host'])
            except:
                traceback.print_exc()
            finally:
                time.sleep(0.01)

    def clean(self):
        while True:
            try:
                for crc32 in list(self.Recv_Transfers.keys()):
                    if self.Recv_Transfers[crc32]['status'] == 2:
                        del self.Recv_Transfers[crc32]
                        continue
                    if time.time() - self.Recv_Transfers[crc32]['atime'] > self.transfer_timeout and self.Recv_Transfers[crc32]['status'] == 0:
                        self.Recv_Transfers[crc32]['status'] = 2

                for crc32 in list(self.Send_Transfers.keys()):
                    if time.time() - self.Send_Transfers[crc32]['atime'] > self.clean_time_interval:
                        del self.Send_Transfers[crc32]
            except:
                traceback.print_exc()
            finally:
                time.sleep(self.clean_time_interval)

    def SendTo(self, send_data, dest_host):
        # print('send:',send_data)
        send_data_list = split_bytes_by_length(send_data, self.buffer_size - 18)
        count = len(send_data_list)
        crc32 = get_crc32(str(dest_host[0] + time.time().__str__()).encode(), 5)
        transfer_pieces = {}
        for piece_id, send_data in enumerate(send_data_list):
            transfer_pieces[piece_id] = {
                'status': 0,
                'data': send_data,
                'ctime': time.time(),
                'atime': time.time()
            }
        self.Send_Transfers[crc32] = {
            'dest_host': dest_host,
            'status': 'A',
            'ctime': time.time(),
            'atime': time.time(),
            'count': count,
            'pieces': transfer_pieces
        }
        self.Send_Handle()

    def Send_Handle(self):
        try:
            for crc32 in list(self.Send_Transfers.keys()):
                if self.Send_Transfers[crc32]['status'] == 'A' or self.Send_Transfers[crc32]['status'] == 'C':
                    for piece_id in list(self.Send_Transfers[crc32]['pieces'].keys()):
                        if self.Send_Transfers[crc32]['pieces'][piece_id]['status'] == 0:
                            if self.Send_Transfers[crc32]['status'] == 'A':
                                header = self.Send_Transfers[crc32]['status'] + crc32 + int_to_hex(
                                    piece_id) + int_to_hex(self.Send_Transfers[crc32]['count'])
                            else:
                                header = self.Send_Transfers[crc32]['status'] + crc32 + int_to_hex(piece_id)
                            send_data = header.encode() + self.Send_Transfers[crc32]['pieces'][piece_id]['data']
                            print('send:',send_data[:100])
                            self.Send_Transfers[crc32]['atime'] = time.time()
                            self.UDP.sendto(send_data, self.Send_Transfers[crc32]['dest_host'])
                            self.Send_Transfers[crc32]['pieces'][piece_id]['status'] = 1
                            self.Send_Transfers[crc32]['pieces'][piece_id]['atime'] = time.time()
                            time.sleep(0.0001)
        except:
            traceback.print_exc()

    def Recv_Handle(self, recv_data, dest_host):
        try:
            data_length = len(recv_data)
            if data_length == 6 and recv_data[:1] == b'E':
                # 完成包
                crc32 = recv_data[1:6].decode()
                if crc32 in list(self.Send_Transfers.keys()):
                    self.Send_Transfers[crc32]['status'] = 'E'

            if data_length == 6 and recv_data[:1] == b'F':
                # 坏包
                crc32 = recv_data[1:6].decode()
                if crc32 in list(self.Send_Transfers.keys()):
                    self.Send_Transfers[crc32]['status'] = 'F'

            if data_length > 12:
                if recv_data[:1] == b'A':
                    # 初始化 带长度
                    crc32 = recv_data[1:6].decode()
                    current_id = hex_to_int(recv_data[6:12].decode())
                    count = hex_to_int(recv_data[12:18].decode())
                    data = recv_data[18:]
                    if current_id > count:
                        print('AAA error')
                    else:
                        self.Join_Data(crc32, current_id, data, count, dest_host)
                        # 收到长度 反馈握手
                        send_data = 'B' + crc32 + int_to_hex(current_id, 6) + int_to_hex(count, 6)
                        self.UDP.sendto(send_data.encode(), dest_host)
                if recv_data[:1] == b'B':
                    '收到 握手反馈 改变状态 不再继续发送长度'
                    crc32 = recv_data[1:6].decode()
                    self.Send_Transfers[crc32]['status'] = 'C'
                if recv_data[:1] == b'C':
                    '无长度继续传输'
                    crc32 = recv_data[1:6].decode()
                    current_id = hex_to_int(recv_data[6:12].decode())
                    data = recv_data[12:]

                    if crc32 in list(self.Recv_Transfers.keys()):
                        self.Join_Data(crc32, current_id, data, 0, dest_host)
                    else:
                        '坏包'
                        send_data = 'F' + crc32
                        self.UDP.sendto(send_data.encode(), dest_host)

                if recv_data[:1] == b'D':
                    '丢包'
                    crc32 = recv_data[1:6].decode()
                    losses = recv_data[6:].decode().split(',')
                    # print('loss_list:',losses[:100])
                    for loss_id in losses:
                        loss_id = int(loss_id)
                        if loss_id > -1:
                            self.Send_Transfers[crc32]['pieces'][loss_id]['status'] = 0
                    self.Send_Handle()
        except:
            traceback.print_exc()


# if __name__ == '__main__':
#     # target = ('10.10.10.231', 8080)
#     # target = ('34.96.165.135', 8080)
#     target = ('127.0.0.1', 8080)
#     c = client(1024)
#     send_data = b'a' * 1024 * 1024 * 10
#     print('crc hash:', zlib.crc32(send_data))
#     c.SendTo(send_data, target)
#     c.Send_Handle()

'''
a = 1
a = 99999999
b = hex(a)
b = '0x' + b.replace('0x', '').rjust(12, '0')
c = int(b, 16)
print(int('0xffffff', 16) / 1024 / 1024)
print(a, b, c)
# A 开始传输数据 初始化是带长度的
print('A 开始传输数据 初始化是带长度的 当前传输方状态为A')
demo = b'ACRC32000001FFFFFFDATA'
print(demo[:1])  # 状态码
print(demo[1:6])  # 数据包的crc截取值
print(demo[6:12])  # 位置
print(demo[12:18])  # 总量
print(demo[18:])  # 数据部分
print(len(demo))
# B 收到长度后反馈 完成握手 表示已经收到
print('B 收到长度后接收端发送反馈 完成握手 表示已经收到 设置传输方状态为C')
demo = b'BCRC32000001FFFFFF'
print(demo[:1])
print(demo[1:6])
print(demo[6:12])
print(demo[12:18])
print(len(demo))
# C 握手成功后继续发送数据包
print('C 收到长度后反馈 完成握手 表示已经收到 当前传输方状态为C')
demo = b'CCRC32000002DATA'
print(demo[:1])
print(demo[1:6])
print(demo[6:12])
print(demo[12:])
# D 接收方检测到传输超时，分析是否完成或者丢包 如果丢包 发送D
print('D 接收方检测到传输超时，分析是否完成或者丢包 如果丢包 发送D')
demo = b'DCRC32[dbxl]'
print(demo[:1])
print(demo[1:6])
print(demo[6:])
# E 接收方检测到传输超时， 分析是否完成或丢包 如果完成 发送E
demo = b'ECRC32'
print(demo[:1])
print(demo[1:6])
# F 异常包
'''
